#include <string.h>
#include <strings.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <time.h> 


#include "ServeurTasRepartie.h"
#include "Message.h"

/* Variable global */
int booleanTasInitie = 0;
server* serverActuelle = NULL;

void* thread_gestion_client(void* arg){
    descriptorListClientConnecteElement* myClient = (descriptorListClientConnecteElement*)arg;
    int nbCharLu = 0, codeMessage = 0;
    printf("Thread vivant socketID : %d, ThreadID : %lu\n", myClient->socketID, myClient->nbThread);

    char recvBuff[64];

    while(1){
        nbCharLu = read_message(myClient->socketID, recvBuff, 64);
        codeMessage = atoi(recvBuff);
        if( strlen(recvBuff) == 0){
            pthread_exit(NULL);
        }
        if(nbCharLu >= 0){
            switch (codeMessage)
            {
            case INIT_TAS:
              printf("INIT_TAS !\n");
              gererInitTas(myClient);

              break;
            case T_MALLOC:
              printf("T_MALLOC !\n");
              gererMalloc(myClient);

              break;
            case T_ACCESS_READ:
              printf("T_ACCESS_READ !\n");
              gererAccessRead(myClient);

              break;
            case T_ACCESS_WRITE:
              printf("T_ACCESS_WRITE !\n");
              gererAccessWrite(myClient);
              break;
            case T_RELEASE:
              /* Pas implementation */
              printf("T_RELEASE !\n");
              break;
            case F_FREE:
              printf("F_FREE !\n");
              gererFree(myClient);
              break;
            default:
              printf("Aucune action disponible !\n");
              break;
            }
        }
    }
   pthread_exit(NULL);
}
/* Sert à demander la taille du tas a allouer au besoin ou retourne la taille du tas existant. */
void gererInitTas(descriptorListClientConnecteElement* myClient){
    char recvBuff[64];
    int tailleTasTmp, nbCharLu;
    int* msg = (int*) malloc(sizeof(int));
    *msg = TAILLE_TAS;

    send_message(myClient->socketID, (void*)msg, sizeof(msg));
    server* srvTmp = newServer();
    nbCharLu = read_message(myClient->socketID, recvBuff, 64);
    pthread_mutex_lock(&(srvTmp->lockServer));
    tailleTasTmp = atoi(recvBuff);
    if(nbCharLu >= 0){
       srvTmp = initServeur(tailleTasTmp);
    }

    int* taille = (int*) malloc(sizeof(int));
    *taille = srvTmp->tailleTas;
    pthread_mutex_unlock(&(srvTmp->lockServer));
    send_message(myClient->socketID, (void*)taille, sizeof(int));
    printf("Tas allouer a %d\n", srvTmp->tailleTas);

    fflush(stdout);
}


void gererMalloc(descriptorListClientConnecteElement* myClient){
    char recvBuff[64], recvBuffNom[64];
    void* recvData;
    int nbCharLu;

    int tailleMallocTmp;
    char* nomDataTemp;
    void* contentDataTmp;

    int* msg = (int*) malloc(sizeof(int));
    *msg = TAILLE_MALLOC;
    send_message(myClient->socketID, (void*)msg, sizeof(msg));
    nbCharLu = read_message(myClient->socketID, recvBuff, 64);
    if(nbCharLu >= 0){
        tailleMallocTmp = atoi(recvBuff);
        printf("Taille recu %d\n", tailleMallocTmp);
    }

    *msg = NOM_VARIABLE_MALLOC;
    send_message(myClient->socketID, (void*)msg, sizeof(msg));

    nbCharLu = 0;
    nbCharLu = read_message(myClient->socketID, recvBuffNom, 64);
    if(nbCharLu >= 0){
        nomDataTemp = recvBuffNom;
        printf("Nom recu à save : %s\n", nomDataTemp);

    }
    /*Malloc pour la data*/
    *msg = DATA_VARIABLE_MALLOC;
    send_message(myClient->socketID, (void*)msg, sizeof(msg));

    nbCharLu = 0;
    contentDataTmp = malloc(tailleMallocTmp);
    recvData = malloc(tailleMallocTmp);
    nbCharLu = read_message(myClient->socketID, recvData, tailleMallocTmp);
    if(nbCharLu >= 0){
        contentDataTmp = recvData;
        printf("Contenu recu a save: %s\n", contentDataTmp);
    }

    pthread_mutex_lock(&(serverActuelle->espaceLibre->lockFreeList));
    pthread_mutex_lock(&(serverActuelle->donneeServer->lockDataList));

    dataDescriptor* newDD = addDataDescriptor(nomDataTemp, tailleMallocTmp, contentDataTmp);
    
    pthread_mutex_unlock(&(serverActuelle->donneeServer->lockDataList));
    pthread_mutex_unlock(&(serverActuelle->espaceLibre->lockFreeList));
    printf("Apres unlock T_MALLOC\n");
    fflush(stdout);
}

void gererAccessRead(descriptorListClientConnecteElement* myClient){
    
    char recvBuffNom[64];
    int nbCharLu;

    char* nomDataTemp;
    void* contentDataTmp;

    int* msg = (int*) malloc(sizeof(int));
    nbCharLu = 0;
    nbCharLu = read_message(myClient->socketID, recvBuffNom, 64);
    if(nbCharLu >= 0){
        nomDataTemp = recvBuffNom;
        printf("Nom recu à chercher : %s\n", nomDataTemp);

    }
    
    dataDescriptor* ddTmp = findDataDescriptor(nomDataTemp);


    if(ddTmp != NULL){
        pthread_mutex_lock(&(ddTmp->lockData));

        *msg = ddTmp->sizeData;
        send_message(myClient->socketID, (void*)msg, sizeof(msg));

        /* Envoie des donnee */

        contentDataTmp = malloc(ddTmp->sizeData);

        memcpy(contentDataTmp, ddTmp->emplacementData, ddTmp->sizeData);

        printf("\nDonne envoyer : \nNom : %s\nContenu : %s \nTaille %d\n", ddTmp->nameData, contentDataTmp, ddTmp->sizeData);

        send_message(myClient->socketID, contentDataTmp, ddTmp->sizeData);
        
        pthread_mutex_unlock(&(ddTmp->lockData));

        printf("Unlock thread : AccessRead\n");

        fflush(stdout);
    }else{
        printf("Nom : %s introuvable \n", recvBuffNom);
    }
}

void gererAccessWrite(descriptorListClientConnecteElement* myClient){

    char recvBuffNom[64], recvBuff[64];
    int nbCharLu, tailleNewData;

    char* nomDataTemp;
    void* contentDataTmp;
    void* recvData;
    void* newContentDataTmp;

    int* msg = (int*) malloc(sizeof(int));
    nbCharLu = 0;
    nbCharLu = read_message(myClient->socketID, recvBuffNom, 64);
    if(nbCharLu >= 0){
        printf("Nom recu : %s\n", recvBuffNom);
        nomDataTemp = recvBuffNom;
        printf("Nom recu à chercher : %s\n", nomDataTemp);

    }
    
    dataDescriptor* ddTmp = findDataDescriptor(nomDataTemp);


    if(ddTmp != NULL){
        pthread_mutex_lock(&(ddTmp->lockData));

        *msg = ddTmp->sizeData;
        send_message(myClient->socketID, (void*)msg, sizeof(msg));

        /* Envoie des donnee */

        contentDataTmp = malloc(ddTmp->sizeData);

        memcpy(contentDataTmp, ddTmp->emplacementData, ddTmp->sizeData);

        printf("Donne envoyer : nom : %s, contenu : %s taille %d\n", ddTmp->nameData, contentDataTmp, ddTmp->sizeData);

        send_message(myClient->socketID, contentDataTmp, ddTmp->sizeData);

        /* Attente des nouvelles donnée taille puis new data */

        nbCharLu = read_message(myClient->socketID, recvBuff, 64);
        if(nbCharLu >= 0){
            tailleNewData = atoi(recvBuff);
            printf("Nouvelle taille recu %d\n ancienne taille %d\n", tailleNewData, ddTmp->sizeData);
        }
        /*Malloc pour la data*/
        *msg = DATA_VARIABLE_REWRITE;
        send_message(myClient->socketID, (void*)msg, sizeof(msg));

        nbCharLu = 0;
        newContentDataTmp = malloc(tailleNewData);
        recvData = malloc(tailleNewData);
        nbCharLu = read_message(myClient->socketID, recvData, tailleNewData);
        if(nbCharLu >= 0){
            newContentDataTmp = recvData;
            printf("Contenu recu a save: %s\n", newContentDataTmp);
        }

        memcpy(ddTmp->emplacementData, newContentDataTmp, tailleNewData);

        pthread_mutex_unlock(&(ddTmp->lockData));

        fflush(stdout);
    }else{
        printf("Nom : %s introuvable \n", recvBuffNom);
    }
}

void gererFree(descriptorListClientConnecteElement* myClient){
    char recvBuffNom[64];
    int nbCharLu;

    char* nomDataTemp;
    nbCharLu = 0;
    nbCharLu = read_message(myClient->socketID, recvBuffNom, 64);
    if(nbCharLu >= 0){
        nomDataTemp = recvBuffNom;
        printf("Nom recu à supprimer : %s\n", nomDataTemp);

    }
    
    dataDescriptor* ddTmp = findDataDescriptor(nomDataTemp);

    if(ddTmp != NULL){
        pthread_mutex_lock(&(serverActuelle->espaceLibre->lockFreeList));
        pthread_mutex_lock(&(serverActuelle->donneeServer->lockDataList));
        pthread_mutex_lock(&(ddTmp->lockData));

        deleteDataDescriptor(nomDataTemp);
        
        pthread_mutex_unlock(&(ddTmp->lockData));
        pthread_mutex_unlock(&(serverActuelle->donneeServer->lockDataList));
        pthread_mutex_unlock(&(serverActuelle->espaceLibre->lockFreeList));
        printf("Unlock thread : FREE\n");

        fflush(stdout);
    }else{
        printf("Nom : %s introuvable \n", recvBuffNom);
    }

}


int affiche_adresse_socket (int sock){
	struct sockaddr_in adresse;
	socklen_t longueur;
	longueur = sizeof(struct sockaddr_in);
	/*Info local*/
	if (getsockname(sock, (struct sockaddr*) &adresse, &longueur) < 0){
		perror("Server : error getsockname\n");
		return -1;
	}
	fprintf(stdout, "Local ==> IP = %s, Port = %u  Socket = %d\n", inet_ntoa(adresse.sin_addr),ntohs(adresse.sin_port), sock);
	
    /*Info distante*/
	if (getpeername(sock, (struct sockaddr*) &adresse, &longueur) < 0){
		perror("Server : error getpeername\n");
		return -1;
	}
	fprintf(stdout, "Distant ==> IP = %s, Port = %u Socket = %d\n", inet_ntoa(adresse.sin_addr),ntohs(adresse.sin_port), sock);
	return 0;
}

void send_message(int socket_fd, void* buffer, int sizeofBuffer)
{
    if ((send(socket_fd,buffer, sizeofBuffer,0))== -1) {
        fprintf(stderr, "Server : Failure Sending Message\n");
        close(socket_fd);
        pthread_exit(NULL);
    }
}

int read_message(int sockfd, void* recvBuff, int sizeofBuffer)
{
	int n=0;
    bzero(recvBuff,sizeofBuffer);

    n = read(sockfd,recvBuff,sizeofBuffer);
    if (n < 0)
    {
        perror("ERROR reading from socket");
        pthread_exit(NULL);
    }
    return n;
}

int serveur_tcp()
{
    int listenfd = 0, thread = 0;
    struct sockaddr_in serv_addr; 
    struct sockaddr_in exp;
    int fromlen = sizeof (exp);
    char sendBuff[1025];
    if ((listenfd = socket(AF_INET,SOCK_STREAM,0)) < 0) {
        perror("socket");
        exit(1);
    }

    memset((void*)&serv_addr, 0, sizeof(serv_addr));
    memset(sendBuff, '0', sizeof(sendBuff));

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(5000); 

    int reuse = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int));
 
    if (bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("bind");
        exit(2);
    }
    listen(listenfd, 100); 
    printf("Mon pid : %d\n", getpid());
    while(1)
    {
        descriptorListClientConnecteElement* nouveauClient = newDescriptorListClientConnecteElement();
     if ( (nouveauClient->socketID = accept(listenfd, (struct sockaddr*)&exp, (socklen_t *)&fromlen))== -1) {
        perror("accept");
        exit(2);
        }

        printf("Socket ID : %d\n", nouveauClient->socketID);
        affiche_adresse_socket(nouveauClient->socketID);
        pthread_t tmp;
        thread = pthread_create(&tmp, NULL, &thread_gestion_client, nouveauClient);
        nouveauClient->nbThread = tmp;
        listClientConnecteElement* newlcce = addListClientConnecteElement(nouveauClient);
        addListClientConnecteElementToList(newlcce);
        if(thread != 0){
            perror("Erreur creation thread\n");
            exit(-1);
        }
    }
    return 0;
}

int main(int argc, char const *argv[])
{
    printf("Argument : %s\n", argv[argc] );
    serverActuelle = newServer();

	return serveur_tcp();
}